﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using log4net;
using log4net.Appender;
using log4net.Layout;
using log4net.Repository.Hierarchy;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Ultima.Xna.Diagnostics;
using Ultima.Xna.Extensions;
using Ultima.Xna.Graphics.UI;
using Ultima.Xna.Input;

namespace Ultima.Xna.Graphics
{
    public interface IConsoleService : IAppender, IDrawable, IGameComponent
    {
        bool IsOpen { get; set; }
    }

    public struct ConsoleString
    {
        public Color Color;
        public string Line;

        public ConsoleString(string line, Color color)
        {
            Line = line;
            Color = color;
        }
    }

    public enum GameConsoleState
    {
        Closed,
        TransitionOpen,
        TransitionClose,
        Open
    }

    public interface ICommand
    {
        Game Game { get; set; }

        string Keyword { get; }
        string Description { get; }

        void Execute(GameTime gameTime, string args);
    }

    public abstract class Command : ICommand
    {
        static ILoggingService _log = new Ultima.Xna.Diagnostics.Logger("Command");

        public static ILoggingService Log
        {
            get { return Command._log; }
            set { Command._log = value; }
        }

        private Game _game;

        public Game Game
        {
            get { return _game; }
            set { _game = value; }
        }

        public Command(Game game)
        {
            _game = game;
        }

        public abstract string Keyword { get; }
        public abstract string Description { get; }

        public virtual void Execute(GameTime gameTime, string args)
        {
            Command.Log.Info("Executing: {0} {1}", Keyword, args);
        }
    }

    public class LoadGumpCommand : Command
    {
        public override string Keyword { get { return "loadgump"; } }
        public override string Description { get { return "Loads a gump (this should only be used for testing purposes)"; } }

        public LoadGumpCommand(Game game) : base(game) { }

        public override void Execute(GameTime gameTime, string args)
        {
            base.Execute(gameTime, args);

            IUIService ui = Game.Services.GetService<IUIService>();

            if (ui != null)
            {
                string[] split = args.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);

                if(split.Length <= 0)
                {
                    Log.Warn("Usage: loadgump <gumpname>");
                    return;
                }

                if (ui.PredefinedComponents.ContainsKey(split[0]) ||
                    ui.PredefinedXmlComponents.ContainsKey(split[0]))
                {
                    ui.CreateInstance(split[0], true);
                }
                else
                {
                    Log.Warn("Registered component '{0}' was not found", split[0]);
                }
            }
            else
            {
                Log.Warn("UI Service not found, unable to execute command");
            }
        }
    }

    public class HelpCommand : Command
    {
        public override string Keyword { get { return "help"; } }
        public override string Description { get { return "Displays help information."; } }

        public HelpCommand(Game game) : base(game) { }

        public override void Execute(GameTime gameTime, string args)
        {
            base.Execute(gameTime, args);

            foreach (ICommand command in GameConsole.RegisteredCommands.Values)
            {
                Command.Log.Info("{0,-15}{1}", command.Keyword, command.Description);
            }
        }
    }

    public class FpsCommand : Command
    {
        public override string Keyword { get { return "fps"; } }
        public override string Description { get { return "Displays the current frames per second of the game."; } }

        public FpsCommand(Game game) : base(game) { }

        public override void Execute(GameTime gameTime, string args)
        {
            base.Execute(gameTime, args);

            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

            if(elapsed != 0)
            {
                Command.Log.Info("Fps: {0}", 1f / elapsed);
            }
            else
            {
                Command.Log.Info("Unable to calculate fps...");
            }
        }
    }

    public class GameConsole : DrawableGameComponent, IConsoleService
    {
        static Dictionary<string, ICommand> _registeredCommands = new Dictionary<string, ICommand>();

        internal static Dictionary<string, ICommand> RegisteredCommands
        {
            get { return GameConsole._registeredCommands; }
        }

        public static void Initialize(Game game)
        {
            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();

            for (int i = 0; i < assemblies.Length; i++)
            {
                Type[] types = assemblies[i].GetTypes();

                for (int j = 0; j < types.Length; i++)
                {
                    if (types[i].GetInterface("ICommand") != null)
                    {
                        Register((ICommand)Activator.CreateInstance(types[i], game));
                    }
                }
            }
        }

        public static void Register(ICommand command)
        {
            if (_registeredCommands.ContainsKey(command.Keyword))
            {
                _registeredCommands[command.Keyword] = command;
            }
            else
            {
                _registeredCommands.Add(command.Keyword, command);
            }
        }

        private string _name = "Game Console";
        private ILayout _layout;
        private TextWriter _writer;
        private MemoryStream _bufferStream;
        private ConsoleString[] _lines;
        private SpriteFont _font;
        private int _level;
        private GameConsoleState _state = GameConsoleState.Closed;
        private bool _initialized;
        private Texture2D _backgroundTexture;
        private SpriteBatch _spriteBatch;
        private float _lineHeight;
        private float _elapsed;
        private float _transitionTime = 0.5f;
        private IInputService _inputService;
        private ILoggingService _loggingService;
        private string _command = string.Empty;

        public int Level
        {
            get { return _level; }
            set { _level = value; }
        }

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        public bool IsOpen
        {
            get { return _state == GameConsoleState.Open; }
            set
            {
                if (value &&
                    _state != GameConsoleState.Open)
                {
                    if (_state != GameConsoleState.TransitionOpen)
                    {
                        _elapsed = 0;
                    }

                    _state = GameConsoleState.TransitionOpen;
                }
                else if(_state != GameConsoleState.Closed)
                {
                    if (_state != GameConsoleState.TransitionClose)
                    {
                        _elapsed = 0;
                    }
                    
                    _state = GameConsoleState.TransitionClose;
                }
            }
        }

        public GameConsole(Game game)
            : base(game)
        {
            _lines = new ConsoleString[20];
            _layout = new PatternLayout();
            _bufferStream = new MemoryStream();
            _writer = new StreamWriter(_bufferStream);
            _inputService = game.Services.GetService<IInputService>();
            _loggingService = game.Services.GetService<ILoggingService>();

            for (int i = 0; i < _lines.Length; i++)
            {
                _lines[i] = new ConsoleString(string.Empty, Color.White);
            }

            Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
            log4net.Repository.Hierarchy.Logger rootLogger = hierarchy.Root;

            if (rootLogger.GetAppender(this.Name) == null)
            {
                rootLogger.AddAppender(this);
            }
        }

        public override void Initialize()
        {
            base.Initialize();

            _initialized = true;
        }

        protected override void LoadContent()
        {
            base.LoadContent();

            _spriteBatch = new SpriteBatch(GraphicsDevice);
            _font = Game.Content.Load<SpriteFont>("Fonts\\Console");
            _lineHeight = _font.MeasureString("HEIGHT").Y;

            _backgroundTexture = new Texture2D(GraphicsDevice, 1, 1);
            _backgroundTexture.SetData<Color>(new Color[] { Color.Black });            
        }

        public void Close()
        {
            _writer.Close();
            _writer.Dispose();
        }

        public void DoAppend(log4net.Core.LoggingEvent loggingEvent)
        {
            _layout.Format(_writer, loggingEvent);

            string line;

            if (_initialized)
            {
                PresentationParameters pp = GraphicsDevice.PresentationParameters;
                line = Utility.WrapText(_font, loggingEvent.MessageObject.ToString(), pp.BackBufferWidth);
            }
            else
            {
                line = loggingEvent.MessageObject.ToString();

            }

            string[] lines = line.Split('\n');

            for (int i = 0; i < _lines.Length - lines.Length; i++)
            {
                _lines[i] = _lines[i + lines.Length];
            }

            if (lines.Length > _lines.Length)
            {
                string[] temp = new string[_lines.Length];

                int j = 0;
                for (int i = lines.Length - temp.Length; i < lines.Length; i++, j++)
                {
                    temp[j] = lines[i];
                }

                lines = temp;
            }

            for (int i = 0; i < lines.Length; i++)
            {
                _lines[_lines.Length - (lines.Length - i)] = new ConsoleString(lines[i], GetLevelColor(loggingEvent.Level));
            }
        }

        private Color GetLevelColor(log4net.Core.Level level)
        {
            switch (level.Name)
            {
                case "DEBUG": return Color.LightGray;
                case "WARN": return Color.Yellow;
                case "FATAL":
                case "ERROR": return Color.Red;
                default: return Color.White;
            }
        }

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            if (_state == GameConsoleState.TransitionClose || _state == GameConsoleState.TransitionOpen)
            {
                _elapsed += (float)gameTime.ElapsedGameTime.TotalSeconds;

                if (_elapsed >= _transitionTime)
                {
                    _elapsed = 0;

                    switch (_state)
                    {
                        case GameConsoleState.TransitionOpen:
                            _state = GameConsoleState.Open;
                            break;
                        case GameConsoleState.TransitionClose:
                            _state = GameConsoleState.Closed;
                            break;
                    }
                }
            }

            if (IsOpen)
            {
                _inputService.HandleKeyboardInput(ref _command);

                if (_inputService.IsKeyPress(Microsoft.Xna.Framework.Input.Keys.Enter))
                {
                    ExecuteCommand(gameTime, _command);
                    _command = string.Empty;
                }
            }
        }

        private void ExecuteCommand(GameTime gameTime, string command)
        {
            string[] split = command.Split(' ');

            if (split.Length > 0 && _registeredCommands.ContainsKey(split[0]))
            {
                ICommand cmd = _registeredCommands[split[0]];

                cmd.Game = Game;
                cmd.Execute(gameTime, String.Join(" ", split, 1, split.Length - 1));
            }
            else
            {
                _loggingService.Warn("Unrecognized Command: {0}", command);
            }
        }

        public override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);

            PresentationParameters pp = GraphicsDevice.PresentationParameters;

            float alpha = 0;

            switch (_state)
            {
                case GameConsoleState.Open:
                    alpha = 1;
                    break;
                case GameConsoleState.TransitionClose:
                    alpha = 1f - (_elapsed / _transitionTime);
                    break;
                case GameConsoleState.TransitionOpen:
                    alpha = (_elapsed / _transitionTime);
                    break;
            }

            _spriteBatch.Begin();
            _spriteBatch.Draw(_backgroundTexture,
                new Rectangle(0, 0, pp.BackBufferWidth, (int)(_lineHeight * (_lines.Length + 1))),
                new Color(new Vector4(1, 1, 1, alpha)));

            for (int i = 0; i < _lines.Length; i++)
            {
                _spriteBatch.DrawString(_font, _lines[i].Line, 
                    new Vector2(5, _lineHeight * i), 
                    new Color(new Vector4(_lines[i].Color.ToVector3(), alpha)));
            }

            _spriteBatch.DrawString(_font, "> " + _command, new Vector2(5, (_lines.Length) * _lineHeight), new Color(new Vector4(Color.Lime.ToVector3(), alpha)));

            _spriteBatch.End();
        }
    }
}
